﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using CloudShot.Entities;
using CloudShot.Interfaces;

namespace CloudShot.Services
{
  // NativeWindow class to listen to operating system messages.
  [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
  internal class HotkeyService : NativeWindow, IHotkeyService
  {
    #region HotkeyInfo

    private class HotkeyInfo
    {
      public Hotkey Hotkey { get; set; }
      public int HotkeyId { get; set; }
      public EventHandler Callback { get; set; }
    }

    #endregion

    #region WinApi

    private const int WM_HOTKEY = 0x0312;

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool RegisterHotKey(IntPtr hWnd, // handle to window    
                                              int id, // hot key identifier    
                                              KeyModifiers fsModifiers, // key-modifier options    
                                              Keys vk // virtual-key code    
      );

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool UnregisterHotKey(IntPtr hWnd, // handle to window    
                                                int id // hot key identifier    
      );

    #endregion

    private static int _hotkeyIdCounter = 100;
    private readonly Form _owner;

    private readonly List<HotkeyInfo> _registeredHotkeys = new List<HotkeyInfo>();

    public HotkeyService(Form owner)
    {
      _owner = owner;
      _owner.HandleCreated += OnHandleCreated;
      _owner.HandleDestroyed += OnHandleDestroyed;
    }

    public bool RegisterHotkey(Hotkey hotkey, EventHandler callback)
    {
      if (!RegisterHotKey(_owner.Handle, _hotkeyIdCounter, hotkey.Modifiers, hotkey.Key))
      {
        Trace.TraceInformation("Warning: Can't register hotkey: {0}\r\n{1}", hotkey, DateTime.Now);
        return false;
      }

      var hkInfo = new HotkeyInfo
      {
        Hotkey = hotkey,
        Callback = callback,
        HotkeyId = _hotkeyIdCounter
      };

      _registeredHotkeys.Add(hkInfo);
      _hotkeyIdCounter++;

      return true;
    }

    public bool UnregisterHotkey(Hotkey hotkey)
    {
      HotkeyInfo hkInfo = _registeredHotkeys.FirstOrDefault(i => i.Hotkey == hotkey);

      if (hkInfo == null)
        return false;

      bool result = UnregisterHotKey(Handle, hkInfo.HotkeyId);
      _registeredHotkeys.Remove(hkInfo);

      return result;
    }

    public void ClearAllHotkeys()
    {
      foreach (HotkeyInfo hotkeyInfo in _registeredHotkeys)
      {
        UnregisterHotKey(Handle, hotkeyInfo.HotkeyId);
      }

      _registeredHotkeys.Clear();
    }

    [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
    protected override void WndProc(ref Message m)
    {
      // Listen for operating system messages
      switch (m.Msg)
      {
        case WM_HOTKEY:

          int id = m.WParam.ToInt32();
          HotkeyInfo hkInfo = _registeredHotkeys.FirstOrDefault(i => i.HotkeyId == id);

          if (hkInfo != null && hkInfo.Callback != null)
          {
            hkInfo.Callback(_owner, EventArgs.Empty);
          }

          break;
      }

      base.WndProc(ref m);
    }

    // Listen for the control's window creation and then hook into it.
    private void OnHandleCreated(object sender, EventArgs e)
    {
      // Window is now created, assign handle to NativeWindow.
      AssignHandle(((Form)sender).Handle);
    }

    private void OnHandleDestroyed(object sender, EventArgs e)
    {
      // Remove all hotkeys
      foreach (HotkeyInfo hkInfo in _registeredHotkeys)
      {
        // _owner.Handle doesn't exist, but this.Handle is the same and still exist
        UnregisterHotKey(Handle, hkInfo.HotkeyId);
      }

      _registeredHotkeys.Clear();

      // Window was destroyed, release handle.
      ReleaseHandle();
    }
  }
}